/* * Sun Public License Notice * * The contents of this file are subject to the Sun Public License * Version 1.0 (the "License"). You may not use this file except in * compliance with the License. A copy of the License is available at * http://www.sun.com/ * * The Original Code is Forte for Java, Community Edition. The Initial * Developer of the Original Code is Sun Microsystems, Inc. Portions * Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved. */ package org.openide.explorer.propertysheet; import java.awt.Component; import java.awt.event.*; import java.beans.*; import java.lang.reflect.*; import java.text.MessageFormat; import java.util.ResourceBundle; import java.util.Vector; import org.openide.NotifyDescriptor; import org.openide.TopManager; import org.openide.explorer.propertysheet.editors.NodePropertyEditor; import org.openide.nodes.Node; /** * Manage a set of node properties from different nodes (but all the same name and type). * Provides * information about the properties, such as reading the property value and so on. * If an application is interested in knowing when the <code>PropertyDetails</code> changes the value of a property, * it can register itself as a listener for property change events by calling * {@link PropertyDisplayer#addPropertyChangeListener}. * * @author Jan Jancura * @version 0.18, Jan 23, 1998 */ final class PropertyDetails extends Object { // static .................................................................................. /** Normal read access, i.e. for simple properties. */ public static final int NORMAL = 1; /** Indexed property read access. */ public static final int INDEXED = 2; /** Normal and/or indexed property read access. */ public static final int BOTH = 3; static java.util.ResourceBundle bundle = org.openide.util.NbBundle.getBundle ( PropertyDetails.class ); // variables ............................................................................... /** Set of PropertyDescriptors of this property (in each bean this property has another PropertyDescriptor). * @associates Property*/ private Vector property = new Vector (3, 3); /** nodes this property display value for */ private Node[] nodes; private Node.Property firstProperty; private PropertyEditor propertyEditor = null; private boolean propertyEditorReaded = false; private PropertyEditor indexedPropertyEditor = null; private boolean indexedPropertyEditorReaded = false; private Class type; private Class elementType; // init ............................................................................... /** * Create new property details object with one property in it. * * @param aProperty the property */ public PropertyDetails (Node[] nodes, Node.Property aProperty) { firstProperty = aProperty; property.addElement (aProperty); this.nodes = nodes; } // other methods ....................................................................... /** * Add a new property to this set. * Must have the same name and value type as all the rest. * @param aProperty the property to add * @return <CODE>true</CODE> if it was really added; <code>false</code> if it was not (because it did not match the others) */ public boolean addProperty (Node.Property aProperty) { if ((aProperty == null) || !aProperty.equals (firstProperty)) return false; property.addElement (aProperty); return true; } /** Getter for nodes attached to this property. */ public Node[] getNodes () { return nodes; } /** * Get the name of contained properties. * * @return the name */ public String getName () { return firstProperty.getDisplayName (); } /** * Test whether the property value type is an array, or other indexed property. * * @return <code>true</code> if so */ boolean isArray () { return (firstProperty instanceof Node.IndexedProperty) || (getValueType ().isArray ()); } //******************************************************************************* /** * Get the value of the <em>first</em> property. * The property must have a getter. * * @return value of the property */ public Object getPropertyValue () throws Exception { return firstProperty.getValue (); } /** * Get the values of all properties. * All the properties must have getters. * * @return values of all the properties (in order of addition) */ public Object[] getPropertyValues () throws Exception { int i, k = property.size (); Object[] o = new Object [k]; for (i = 0; i < k; i++) o [i] = ((Node.Property)property.elementAt (i)).getValue (); return o; } /** * Get the value of the <em>first</em> property at some index. * This first property must be an array or indexed. * * @param index index of property to get * @return value of the first property at that index */ public Object getPropertyValue (int index) throws Exception { if ( (firstProperty instanceof Node.IndexedProperty) && ((Node.IndexedProperty)firstProperty).canIndexedRead () ) return ((Node.IndexedProperty)firstProperty).getIndexedValue (index); Object[] array = (Object[]) getPropertyValue (); return array [index]; } /** * Set the property value (for all contained properties). * * @param value value to set */ public void setPropertyValue (Object value) { int i, k = property.size (); Node.Property prop = null; for (i = 0; i < k; i++) { try { (prop = ((Node.Property)property.elementAt (i))).setValue (value); } catch (final Exception e) { notifyExceptionInSetter (e, prop.getDisplayName ()); } } } /** * Set the property values (specifying individual values for each property). * * @param value values to set, in same order as properties were added */ public void setPropertyValues (Object[] values) { int i, k = property.size (); Node.Property prop = null; for (i = 0; i < k; i++) { try { (prop = ((Node.Property)property.elementAt (i))).setValue (values [i]); } catch (Exception e) { notifyExceptionInSetter (e, prop.getDisplayName ()); } } } /** * Set an indexed value (on all contained properties). * The properties must all have indexed setters. * * @param value the value to set * @param index the array index in each property to set the value at */ public void setPropertyValue (Object value, int index) { int i, k = property.size (); Node.IndexedProperty prop = null; for (i = 0; i < k; i++) { try { (prop = ((Node.IndexedProperty)property.elementAt (i))).setIndexedValue (index, value); } catch (Exception e) { notifyExceptionInSetter (e, prop.getDisplayName ()); } } } /** * Get the property editor (for the first property). * * @return the property editor, or <code>null</code> if it does not exist */ public PropertyEditor getPropertyEditor () { if (propertyEditorReaded) return propertyEditor; propertyEditorReaded = true; return propertyEditor = getNewPropertyEditor (); } /** * Get the indexed property editor (for the first property). * * @return the property editor, or <code>null</code> if it does not exist */ public PropertyEditor getIndexedPropertyEditor () { if (indexedPropertyEditorReaded) return indexedPropertyEditor; indexedPropertyEditorReaded = true; return indexedPropertyEditor = getNewIndexedPropertyEditor (); } /** * Get the property editor (for the first property), creating one afresh. * * @return the property editor, or <code>null</code> if it does not exist */ public PropertyEditor getNewPropertyEditor () { PropertyEditor propertyEditor = firstProperty.getPropertyEditor (); if (propertyEditor instanceof NodePropertyEditor) { NodePropertyEditor np = (NodePropertyEditor)propertyEditor; np.attach (nodes); } if ((propertyEditor == null) && isArray ()) { PropertyEditor pe = getIndexedPropertyEditor (); if (pe == null) return null; return new IndexedPropertyEditor (this); } return propertyEditor; } /** * Get the indexed property editor (for the first property), creating one afresh. * * @return the property editor, or <code>null</code> if it does not exist */ public PropertyEditor getNewIndexedPropertyEditor () { if (firstProperty instanceof Node.IndexedProperty) return ((Node.IndexedProperty)firstProperty).getIndexedPropertyEditor (); return findEditor (getIndexedValueType ()); } /** * Test whether all properties are readable (normally or by index) and have the same value. * <p>Used to indicate whether it is reasonable to display a value representing all of them. * * @return <CODE>true</CODE> if so */ public boolean canRead () throws Exception { return canRead (BOTH); } /** * Test whether all properties are readable and have the same value. * * @param type type of read access desired ({@link #NORMAL}, {@link #INDEXED}, or {@link #BOTH}) * @return <CODE>true</CODE> if so */ public boolean canRead (int type) throws Exception { if (getPropertyEditor () == null) return false; if (!canRead (firstProperty, type)) return false; int i, k = property.size (); if (k == 1) return true; Object value = firstProperty.getValue (), v; Node.Property prop; if (value == null) for (i = 1; i < k; i ++) { prop = (Node.Property)property.elementAt (i); if ((!canRead (prop, type)) || (prop.getValue () != null)) return false; } else for (i = 1; i < k; i ++) { prop = (Node.Property)property.elementAt (i); if ((!canRead (prop, type)) || ((v = prop.getValue ()) == null) || (!value.equals (v))) return false; } return true; } /** * Test whether at least one contained property may be set (normally or by index). * <p>Indicates whether it is useful to provide a UI to set the property across all the nodes. * @return <CODE>true</CODE> if so */ public boolean canWrite () { return canWrite (BOTH); } /** * Test whether at least one contained property may be set. * @param type type of write access desired ({@link #NORMAL}, {@link #INDEXED}, or {@link #BOTH}) * @return <CODE>true</CODE> if so */ public boolean canWrite (int type) { if (getPropertyEditor () == null) return false; int i, k = property.size (); for (i = 0; i < k; i++) if (canWrite ((Node.Property) property.elementAt (i), type)) return true; return false; } /** * Retruns true if property p has getter or indexed getter. */ private static boolean canRead (Node.Property p, int type) { if (p instanceof Node.IndexedProperty) { Node.IndexedProperty ip = (Node.IndexedProperty)p; return (((type & NORMAL) != 0) && ip.canRead ()) || (((type & INDEXED) != 0) && ip.canIndexedRead ()); } return ((type & NORMAL) != 0) && p.canRead (); } /** * Returns true if property has setter or indexed setter. */ private static boolean canWrite (Node.Property p, int type) { if (p instanceof Node.IndexedProperty) { Node.IndexedProperty ip = (Node.IndexedProperty)p; return (((type & NORMAL) != 0) && ip.canWrite ()) || (((type & INDEXED) != 0) && ip.canIndexedWrite ()); } return ((type & NORMAL) != 0) && p.canWrite (); } /** * Test whether the properties are editable. * * @return <CODE>true</CODE> if some properties are writable or if there is a custom editor */ public boolean canEdit () { return canWrite () || hasCustomEditor (); } /** * Test whether all contained properties support a default value. * @return <code>true</code> if so */ public boolean supportsDefaultValue () { int i, k = property.size (); for (i = 0; i < k; i++) if (!((Node.Property) property.elementAt (i)).supportsDefaultValue () ) return false; return true; } /** * Restore the default value for all properties. */ public void restoreDefaultValue () { int i, k = property.size (); Node.Property prop = null; for (i = 0; i < k; i++) { try { (prop = (Node.Property) property.elementAt (i)).restoreDefaultValue (); } catch (Exception e) { notifyExceptionInSetter (e, prop.getDisplayName ()); } } } /** * Test whether all contained properties are expert. * * @return true if so */ public boolean isExpert () { // int i, k = property.size (); // for (i = 0; i < k; i++) // if (!((Node.Property) property.elementAt (i)).isExpert ()) // return false; return false; } /** * Get the short description of the contained properties. * * @return the short description for all properties if they have one and they all match */ public String getShortDescription () { String s = firstProperty.getShortDescription (); int i, k = property.size (); for (i = 1; i < k; i++) if (!((Node.Property) property.elementAt (i)).getShortDescription ().equals (s)) return null; return s; } /** * Test whether the first property has a custom editor. * * @return <CODE>true</CODE> if so */ public boolean hasCustomEditor () { PropertyEditor propertyEditor = getPropertyEditor (); if (propertyEditor == null) return false; return propertyEditor.supportsCustomEditor (); } /** * Get the custom property editor for the first property. * * @return the customizer or <code>null</code> if none */ public Component getPropertyCustomEditor () { PropertyEditor propertyEditor = getPropertyEditor (); if (propertyEditor == null) return null; if (!propertyEditor.supportsCustomEditor ()) return null; return propertyEditor.getCustomEditor(); } /** * Get the custom property editor from any regular property editor. * * @param propertyEditor editor which will be asked for custom editor * @return the custom property editor, or <CODE>null</CODE> if that is not supported */ public static Component getPropertyCustomEditor (PropertyEditor propertyEditor) { if (!propertyEditor.supportsCustomEditor ()) return null; return propertyEditor.getCustomEditor (); } /** * Get the property type for all properties. * * @return the type */ public Class getValueType () { if (type != null) return type; // Indexed properties without nonindexed access return null on getPropertyType // this patch creates array type or them type = firstProperty.getValueType (); try { if (type == null) { // obtains array type [getIndexValueType ()] type = java.lang.reflect.Array.newInstance (getIndexedValueType (), 0).getClass (); } } catch (Exception e) { return type = Object[].class; } return type; } /** * Get the element type for all properties. * * @return the element type, or <code>null</code> if the properties are not arrays or indexed */ public Class getIndexedValueType () { if (elementType != null) return elementType; return elementType = (firstProperty instanceof Node.IndexedProperty) ? ((Node.IndexedProperty) firstProperty).getElementType () : getValueType ().getComponentType (); } /** Finds editor for given class. Registers the property editor * if necessary. * * @param clazz the class * @return the property editor or null */ private PropertyEditor findEditor (Class clazz) { PropertyEditor p = PropertyEditorManager.findEditor (clazz); if (p instanceof NodePropertyEditor) { NodePropertyEditor np = (NodePropertyEditor)p; np.attach (nodes); } return p; } void notifyExceptionInSetter (Exception e, String propertyName) { TopManager.getDefault ().notifyException ( new ExceptionHack (e, propertyName) ); } // innerclasses ........................................................................... /** * Hack for using Exception dialog with Details button. */ class ExceptionHack extends Exception { /** Original exception. */ private Throwable t; /** Localized text. */ private String text; ExceptionHack (Throwable t, String propertyName) { super (""); // NOI18N this.t = t; // JST: this can be improved in future... // HANZ: See NotifyException boolean isLocalized = false; if ( (t.getLocalizedMessage () != null) && (!t.getLocalizedMessage ().equals (t.getMessage ())) ) isLocalized = true; if (isLocalized) text = new MessageFormat ( bundle.getString ("EXC_Setter_localized") ).format (new Object[] { propertyName, t.getLocalizedMessage () }); else text = new MessageFormat ( bundle.getString ("EXC_Setter") ).format (new Object[] { propertyName }); } public String getLocalizedMessage () { return text; } public void printStackTrace (java.io.PrintStream s) { t.printStackTrace (s); } public void printStackTrace (java.io.PrintWriter s) { t.printStackTrace (s); } } } /* * Log * 15 Gandalf 1.14 1/12/00 Ian Formanek NOI18N * 14 Gandalf 1.13 1/10/00 Jan Jancura Bug getLocMessage == * null * 13 Gandalf 1.12 1/5/00 Jan Jancura NotifyException (with * Details button) used for exceptions in setter (Yarda!). * 12 Gandalf 1.11 12/10/99 Jan Jancura Localisation improved * 11 Gandalf 1.10 10/22/99 Ian Formanek NO SEMANTIC CHANGE - Sun * Microsystems Copyright in File Comment * 10 Gandalf 1.9 9/15/99 Jaroslav Tulach More private things & * support for default property. * 9 Gandalf 1.8 6/30/99 Ian Formanek Reflecting package * change of NodePropertYEditor * 8 Gandalf 1.7 6/8/99 Ian Formanek ---- Package Change To * org.openide ---- * 7 Gandalf 1.6 6/3/99 Jaroslav Tulach NodePropertyEditor & * NodeCustomizer * 6 Gandalf 1.5 3/26/99 Jaroslav Tulach * 5 Gandalf 1.4 3/20/99 Jesse Glick [JavaDoc] * 4 Gandalf 1.3 3/20/99 Jesse Glick [JavaDoc] * 3 Gandalf 1.2 3/4/99 Jan Jancura Localization moved * 2 Gandalf 1.1 1/6/99 Jaroslav Tulach Change of package of * DataObject * 1 Gandalf 1.0 1/5/99 Ian Formanek * $ */